Migracja bazy danych
==================

> Note|Uwaga: Funkcjonalność migrowania bazy danych jest dostępna od wersji 1.1.6.

Podobnie jak kod tak i struktura bazy danych zmienia się wraz z rozwojem i zarządzaniem aplikacją bazodanową. Na przykład, podczas rozwoju, możemy chcieć dodać nową tabelę, czy też, po przeniesieniu aplikacji na serwer produkcyjny, może powstać potrzeba dodania indeksu do kolumny. Ważne jest, aby śledzić te zmiany w strukturze bazy danych (nazywane **migracją**) podobnie jak to robimy z kodem. Jeśli kod źródłowy i baza danych rozsynchronizują się, zachodzi duże prawdopodobieństwo, że cały system przestanie działać. Z tego powodu, Yii dostarcza narzędzie do migracji, które może śledzić historię migracji bazy danych, aplikować nowe migracje lub też odwracać istniejące.

Poniższe kroki pokazują sposób używania migracji bazy danych w trakcie rozwoju aplikacji:

1. Jan utworzył nową migrację (np. dodał nową tabelę)
2. Jan skomitował nową migrację do systemu kontroli wersji (np. SVN, GIT)
3. Stefan ściągnął zmiany z systemy kontroli wersji i otrzymał nową migrację
4. Stefan zaaplikował nową migrację na swoją lokalną bazę danych


Yii wspiera migrację bazy danych poprzez narzędzie linii poleceń `yiic migrate`. Pozwala ono na tworzenie nowych, aplikowanie/odwracanie/poprawianie migracji oraz wyświetlanie historii oraz nowych migracji.

W dalszej części opiszemy w jaki sposób używać tego narzędzia.

> Note|Uwaga: Zaleca się używanie narzędzia yiic właściwego dla danej aplikacji (np. `cd ścieżka/do/protected`)
> podczas pracy z komendą `migrate` zamiast tego, który znajduje się w katalogu `framework`.
> Upewnij się, że posiadasz folder `protected\migrations` z prawami zapisu. Sprawdź również, czy
> skonfigurowałeś połączenie z bazą danych w pliku `protected/config/console.php`.

Tworzenie migracji
-------------------

Aby utworzyć nową migrację (np. dodającej nową tabelę), wykonujemy następujące polecenie:

~~~
yiic migrate create <name>
~~~

Obowiązkowy parametr `name` bardzo zwięźle opisuje migrację (np. `create_news_table`). Jak zobaczymy w dalszej części, parametr `name` używany jest jako część nazwy klasy PHP. Z tej przyczyny powinien zawierać jedynie litery, cyfry i/lub znaki podkreślenia.

~~~
yiic migrate create create_news_table
~~~

Powyższe polecenie utworzy w katalogu `protected/migrations` nowy plik o nazwie `m20101129_185401_create_news_table.php`, który zawierać będzie następujący, początkowy kod:

~~~
[php]
class m101129_185401_create_news_table extends CDbMigration
{
	public function up()
	{
	}

    public function down()
    {
		echo "m101129_185401_create_news_table does not support migration down.\n";
		return false;
    }

	/*
	// implement safeUp/safeDown instead if transaction is needed
	public function safeUp()
	{
	}

	public function safeDown()
	{
	}
	*/
}
~~~

Zauważ, że nazwa klasy jest taka sama jak nazwa pliku, zgodnie z wzorcem `m<timestamp>_<name>`, gdzie `<timestamp>` wskazuje na stempel czasu UTC (w domyślnym formacie `yymmdd_hhmmss`) utworzenia migracji, zaś wartość reprezentująca `<name>` pobierana jest z parametru `name` polecenia.

Metoda `up()` powinna zawierać kod implementujący aklualną migrację bazy danych, podczas gdy metoda `down()` może zawierać kod odwracający to, co zostało zrobione w metodzie `up()`.

Czasami niemożliwe jest zaimplementowanie metody `down()`. Na przykład, jeśli usunęliśmy wiersze tabeli w metodzie `up()`, nie będziemy w stanie ich odtworzyć w metodzie `down()`. W takim przypadku migracja nazywana jest nieodwracalną, co oznacza, że nie możemy powrócić do poprzedniego stanu bazy danych. W kodzie wygenerowanym powyżej, metoda `down()` zwraca `false` w celu zasygnalizowania nieodwracalności migracji.

> Info|Info: Poczynając od wersji 1.1.7, jeśli metody `up()` oraz `down()` zwracają
> `false`, wszystkie następne migrację zostaną anulowane. W poprzedniej wersji
> 1.1.6, każda musiała zwrócić wyjątek aby anulować kolejne migracje. 

W ramach przykładu pokażemy migrację tworzącą tabelę wiadomości.

~~~
[php]
class m101129_185401_create_news_table extends CDbMigration
{
	public function up()
	{
		$this->createTable('tbl_news', array(
			'id' => 'pk',
			'title' => 'string NOT NULL',
			'content' => 'text',
		));
	}

	public function down()
	{
		$this->dropTable('tbl_news');
	}
}
~~~

Klasa bazowa [CDbMigration] dostarcza metod manipulujących danymi oraz schematem bazy danych. Na przykład, metoda [CDbMigration::createTable] utworzy tabelę w bazie danych, zaś metoda [CDbMigration::insert] wstawi wiersz danych. Wszystkie te metody używają połączenia z bazą danych zwracanego przez metodę [CDbMigration::getDbConnection()], która domyślnie zwraca `Yii::app()->db`.

> Info|Info: Jak zauważyłeś, metody bazodanowe dostarczane przez [CDbMigration] są bardzo podobne do tych z [CDbCommand]. Rzeczywiście są one prawie takie same z tym wyjątkien, że metody [CDbMigration] odmierzają czas zużyty na ich wykonanie oraz wyświetlają pewne dodatkowe informację o parametrach metody.


Migracje transakcyjne
---------------------

> Info|Info: Wsparcie dla migracji transakcyjnych jest wpierane od wersji 1.1.7.

W trakcie wykonywania skomplikowanych migracji baz danych, zazwyczaj chcemy być pewni, że każda migracja powiedzie się lub nie w całości tak aby baza danych zachowała swoją spójność i integralność. Aby uzyskać ten efekt możemy wykorzystać transakcje baz danych.

Możemy wprost rozpocząć transakcję bazodanową i ująć resztę kodu związanego z bazą w ramach tej transakcji w następujący sposób:

~~~
[php]
class m101129_185401_create_news_table extends CDbMigration
{
	public function up()
	{
		$transaction=$this->getDbConnection()->beginTransaction();
		try
		{
			$this->createTable('tbl_news', array(
				'id' => 'pk',
				'title' => 'string NOT NULL',
				'content' => 'text',
			));
			$transaction->commit();
		}
		catch(Exception $e)
		{
			echo "Exception: ".$e->getMessage()."\n";
			$transaction->rollBack();
			return false;
		}
	}

	// ...podobny kod dla metody down()
}
~~~

Jednakże łatwiejszym sposobem uzyskania wparcia dla transakcyjności jest zaimplementowanie metody `safeUp()` zamiast `up()` oraz `safeDown()` zamiast `down()`. Na przykład:

~~~
[php]
class m101129_185401_create_news_table extends CDbMigration
{
	public function safeUp()
	{
		$this->createTable('tbl_news', array(
			'id' => 'pk',
			'title' => 'string NOT NULL',
			'content' => 'text',
		));
	}

	public function safeDown()
	{
		$this->dropTable('tbl_news');
	}
}
~~~

Podczas przeprowadzania migracji przez framework Yii, rozpocznie on transakcję bazy danych a następnie wywoła metodę `safeUp()` lub `safeDown()`. Jeśli w metodzie `safeUp()` lub `safeDown()` wystąpi błąd na bazie transakcja zostanie wycofana zapewniając w ten sposób poprawny stan bazy danych.

> Note|Uwaga: Nie wszystkie DBMS wspierają transakcje. Również niektóre zapytania nie mogą być umieszczane
> w ramach transakcji. W takim przypadku będziesz musiał zaimplementować metodę `up()` oraz
> `down()`. Dla MySQL, część instrukcji SQL może powodować wywołanie
> [niejawne polecenia COMMIT](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html).


Aplikowanie migracji
-------------------

Aby zaaplikować wszystkie nowe migracje (np. aktualizujące lokalną wersję bazy danych) uruchom następujące polecenie:

~~~
yiic migrate
~~~

Polecenie wyświetli listę wszystkich nowych migracji. Jeśli potwierdzisz zaaplikowanie tych migracji, polecenie to wywoła metodę `up()` każdej z klasy migracyjnych, jedna po drugiej, w kolejności wartości znacznika czasu (ang. timestamp) zapisanego w nazwie klasy.

Po zastosowaniu migracji, narzędzie migrujace zapisze rekord w tabeli `tbl_migration`. Pozwoli to narzędziu zidentyfikować, które migracje zostały zaaplikowane a które nie. Jeśli tabela `tbl_migration` nie istnieje, narzędzie migracji utworzy ją w bazie określonej przez komponent aplikacji `db`.

Czasami możemy chcieć zaaplikować tylko jedną lub kilka migracji. W tym celu używamy następującego polecenia:

~~~
yiic migrate up 3
~~~

Polecenie to zaaplikuje 3 nowe migracje. Zmieniając wartość 3 zmieniamy ilość migracji, które mają zostać zaaplikowane.

Możemy również zmigrować bazę danych do określonej wersji za pomocą następującego polecenia:

~~~
yiic migrate to 101129_185401
~~~

Oznacza to, że korzystamy z części nazwy migracji zawierającej informację o stemplu czasowym w celu określenia wersji, do której chcemy zmigrować bazę danych. Jeśli pomiędzy ostatnio zastosowaną migracją a określoną przez nas występują inne, wszystkie te migracje zostaną zaaplikowane. Jeśli dana migracja została zaaplikowana wcześniej, wtedy wszystkie migracje występujące po niej zostaną odwrócone (zostanie to opisane w dalszej części).


Odwracanie migracji
--------------------

W celu odwrócenia ostaniej lub kilku ostatnio zaaplikowanych migracji, możemy użyć następującego polecenia:

~~~
yiic migrate down [step]
~~~

gdzie opcjonalny parametr `step` określa jak wiele migracji zostanie odwróconych. Domyślnie przyjmuje wartość 1 co oznacza odwracanie ostatnio zaaplikowanej migracji.

Tak jak wspominaliśmy wcześniej nie wszystkie migracje można odwracać. Próbując odwrócić nieodwracalną migrację otrzymamy wyjątek i proces odwracania zostanie przerwany. 


Poprawianie migracji
------------------

Poprawianie migracji oznacza odwrócenie a następnie zaaplikowanie danej migracji jeszcze raz. Może to być zrobione za pomocą następującego polecenia:

~~~
yiic migrate redo [step]
~~~

gdzie opcjonalny parametr `step` określa jak wiele migracji zostanie poprawionych. Domyślnie przyjmuje wartość 1 co oznacza, że ostatnia migracja zostanie poprawiona.


Wyświetlanie informacji o migracji
-----------------------------

Poza aplikowaniem i odwracaniem migracji, narzędzie do migracji potrafi również wyświetlać historię oraz listę nowych, gotowych do zaaplikowania migracji.

~~~
yiic migrate history [limit]
yiic migrate new [limit]
~~~

Opcjonalny parametr `limit` określa ilość migracji do wyświetlenia. Jeśli parametr `limit` nie został zdefiniowany, wyświetlone zostaną wszystkie migracje.

Pierwsze polecenie pokaże migracje, które zostały zaaplikowane, drugie zaś wyświetli migracje, które nie zostały jeszcze zaaplikowane.


Modyfikowanie historii migracji
---------------------------

Czasami możemy chcieć zmodyfikować historię migracji do konkretnej wersji bez aplikowania lub też odwracania migracji. Dzieje się to często, kiedy tworzymy nową migracje. Możemy użyć następującego polecenia aby to zrobić.

~~~
yiic migrate mark 101129_185401
~~~

Polecenie to jest bardzo podobne do `yiic migrate to` z tym wyjątkiem, że modyfikuje ono wyłącznie tabelę zawierającą wpisy o historii migracji tak aby wskazywała ona daną wersję migracji bez jej aplikowania czy też odwracania.


Dostosowywanie poleceń migracji
-----------------------------

Istnieje kilka sposobów aby dostosować polecenia migracji.

### Poprzez opcje linii poleceń

Polecenia migracji dostarczane są wraz z czterema opcjami, które mogą być określone w linii poleceń:

* `interactive`: boolean, określa kiedy wykonywać migrację w trybie interaktywnym. Domyślnie true, co oznacza, iż użytkownik jest monitowany o wynikach migracji podczas jej wykonywania. Jeżeli ustawisz tę wartość na false migracja powinna przebiegać w tle.

* `migrationPath`: łańcuch znaków, określający katalog przechowujący wszystkie pliki klas migracji. Należy go zdefiniować używając aliasu ścieżki do istniejącego katalogu. Jeśli opcja ta nie została określona, będzie ona używać podkatalogu `migrations` znajdującego się w katalogu bazowym aplikacji. 

* `migrationTable`: łańcuch znaków, określający nazwę tabeli bazy danych przechowywującej informacje o historii migracji. Domyślna wartość to `tbl_migration`. Struktura tej tabeli jest następująca `version varchar(255) primary key, apply_time integer`.

* `connectionID`: łańcuch znaków, określający identyfikator komponentu bazy danych aplikacji. Domyślnie 'db'.

* `templateFile`: łańcuch znaków, określający ścieżkę do pliku, który posłuży jako szablon kodu używany do generowania klas migracji. Należy go określić przy użyciu aliasu ścieżki (np. `application.migrations.template`). Jeśli nie został podany, wewnętrzny szablon zostanie użyty. W szablonie token `{ClassName}` zostanie zastąpiony aktualną nazwą klasy migrującej.

W celu zdefiniowania tych opcji wykonaj polecenia migracji używając formatu:

~~~
yiic migrate up --opcja1=wartość1 --opcja2=wartość2 ...
~~~

Na przykład, jeśli chcemy przeprowadzić migrację dla modułu `forum`, którego pliki migracyjne znajdują się w jego wewnętrznym katalogu `migrations`, możemy użyć następującego polecenia:

~~~
yiic migrate up --migrationPath=ext.forum.migrations
~~~


### Globalne konfigurowanie poleceń

Podczas gdy opcje linii poleceń pozwalają nam konfigurować polecenia w locie, czasami możemy chcieć skonfigurować polecenia raz na zawsze. Na przykład, możemy chcieć używać innej tabeli do przechowywania historii migracji, czy też możemy chcież używać zmienionego szablonu migracyjnego. Możemy to zrobić poprzez zmodyfikowanie pliku konfiguracyjnego aplikacji konsolowej następująco:

~~~
[php]
return array(
	......
	'commandMap'=>array(
		'migrate'=>array(
			'class'=>'system.cli.commands.MigrateCommand',
			'migrationPath'=>'application.migrations',
			'migrationTable'=>'tbl_migration',
			'connectionID'=>'db',
			'templateFile'=>'application.migrations.template',
		),
		......
	),
	......
);
~~~

Jeśli teraz uruchomimy polecenie `migrate` powyższa konfiguracja będzie obowiązywać bez konieczności wprowadzania za każdym razem opcji poprzez linię poleceń.


<div class="revision">$Id: database.migration.txt 3450 2011-11-20 22:52:07Z alexander.makarow $</div>